// Imports
use crate::{config, dialogs, RnAppWindow};
use gettextrs::gettext;
use gtk4::graphene;
use gtk4::{
    gdk, gio, glib, glib::clone, prelude::*, PrintOperation, PrintOperationAction, Unit,
    UriLauncher, Window,
};
use mytool_compose::SplitOrder;
use p2d::bounding_volume::BoundingVolume;
use std::path::PathBuf;
use std::str::FromStr;
use std::time::Instant;
use tracing::{debug, error};

const CLIPBOARD_INPUT_STREAM_BUFSIZE: usize = 4096;

impl RnAppWindow {
    /// Boolean actions have no target, and a boolean state. They have a default implementation for the activate signal,
    /// which requests the state to be inverted, and the default implementation for change_state, which sets the state
    /// to the request.
    ///
    /// We generally want to connect to the change_state signal. (but then have to set the state with
    /// `action.set_state()`)
    ///
    /// We can then either toggle the state through activating the action, or set the state explicitly through
    /// `action.change_state(<request>)`
    pub(crate) fn setup_actions(&self) {
        let action_fullscreen = gio::PropertyAction::new("fullscreen", self, "fullscreened");
        self.add_action(&action_fullscreen);
        let action_open_settings = gio::SimpleAction::new("open-settings", None);
        self.add_action(&action_open_settings);
        let action_about = gio::SimpleAction::new("about", None);
        self.add_action(&action_about);
        let action_donate = gio::SimpleAction::new("donate", None);
        self.add_action(&action_donate);
        let action_keyboard_shortcuts_dialog = gio::SimpleAction::new("keyboard-shortcuts", None);
        self.add_action(&action_keyboard_shortcuts_dialog);
        let action_open_canvasmenu = gio::SimpleAction::new("open-canvasmenu", None);
        self.add_action(&action_open_canvasmenu);
        let action_open_appmenu = gio::SimpleAction::new("open-appmenu", None);
        self.add_action(&action_open_appmenu);
        let action_toggle_overview = gio::SimpleAction::new("toggle-overview", None);
        self.add_action(&action_toggle_overview);
        let action_devel_mode =
            gio::SimpleAction::new_stateful("devel-mode", None, &false.to_variant());
        self.add_action(&action_devel_mode);
        let action_devel_menu = gio::SimpleAction::new("devel-menu", None);
        self.add_action(&action_devel_menu);
        let action_new_tab = gio::SimpleAction::new("new-tab", None);
        self.add_action(&action_new_tab);
        let action_visual_debug =
            gio::SimpleAction::new_stateful("visual-debug", None, &false.to_variant());
        self.add_action(&action_visual_debug);
        let action_debug_export_engine_state =
            gio::SimpleAction::new("debug-export-engine-state", None);
        self.add_action(&action_debug_export_engine_state);
        let action_debug_export_engine_config =
            gio::SimpleAction::new("debug-export-engine-config", None);
        self.add_action(&action_debug_export_engine_config);
        let action_righthanded = gio::PropertyAction::new("righthanded", self, "righthanded");
        self.add_action(&action_righthanded);
        let action_touch_drawing = gio::PropertyAction::new("touch-drawing", self, "touch-drawing");
        self.add_action(&action_touch_drawing);
        let action_focus_mode = gio::PropertyAction::new("focus-mode", self, "focus-mode");
        self.add_action(&action_focus_mode);

        let action_pen_sounds =
            gio::SimpleAction::new_stateful("pen-sounds", None, &false.to_variant());
        self.add_action(&action_pen_sounds);
        let action_snap_positions =
            gio::SimpleAction::new_stateful("snap-positions", None, &false.to_variant());
        self.add_action(&action_snap_positions);
        let action_show_format_borders =
            gio::SimpleAction::new_stateful("show-format-borders", None, &true.to_variant());
        self.add_action(&action_show_format_borders);
        let action_show_origin_indicator =
            gio::SimpleAction::new_stateful("show-origin-indicator", None, &true.to_variant());
        self.add_action(&action_show_origin_indicator);
        let action_block_pinch_zoom =
            gio::PropertyAction::new("block-pinch-zoom", self, "block-pinch-zoom");
        self.add_action(&action_block_pinch_zoom);
        let action_respect_borders =
            gio::PropertyAction::new("respect-borders", self, "respect-borders");
        self.add_action(&action_respect_borders);
        let action_pen_style = gio::SimpleAction::new_stateful(
            "pen-style",
            Some(&String::static_variant_type()),
            &String::from("brush").to_variant(),
        );
        self.add_action(&action_pen_style);
        let action_undo_stroke = gio::SimpleAction::new("undo", None);
        self.add_action(&action_undo_stroke);
        let action_redo_stroke = gio::SimpleAction::new("redo", None);
        self.add_action(&action_redo_stroke);
        let action_zoom_reset = gio::SimpleAction::new("zoom-reset", None);
        self.add_action(&action_zoom_reset);
        let action_zoom_fit_width = gio::SimpleAction::new("zoom-fit-width", None);
        self.add_action(&action_zoom_fit_width);
        let action_zoomin = gio::SimpleAction::new("zoom-in", None);
        self.add_action(&action_zoomin);
        let action_zoomout = gio::SimpleAction::new("zoom-out", None);
        self.add_action(&action_zoomout);
        let action_add_page_to_doc = gio::SimpleAction::new("add-page-to-doc", None);
        self.add_action(&action_add_page_to_doc);
        let action_remove_page_from_doc = gio::SimpleAction::new("remove-page-from-doc", None);
        self.add_action(&action_remove_page_from_doc);
        let action_resize_to_fit_content = gio::SimpleAction::new("resize-to-fit-content", None);
        self.add_action(&action_resize_to_fit_content);
        let action_return_origin_page = gio::SimpleAction::new("return-origin-page", None);
        self.add_action(&action_return_origin_page);
        let action_selection_trash = gio::SimpleAction::new("selection-trash", None);
        self.add_action(&action_selection_trash);
        let action_selection_duplicate = gio::SimpleAction::new("selection-duplicate", None);
        self.add_action(&action_selection_duplicate);
        let action_selection_invert_color = gio::SimpleAction::new("selection-invert-color", None);
        self.add_action(&action_selection_invert_color);
        let action_selection_select_all = gio::SimpleAction::new("selection-select-all", None);
        self.add_action(&action_selection_select_all);
        let action_selection_deselect_all = gio::SimpleAction::new("selection-deselect-all", None);
        self.add_action(&action_selection_deselect_all);
        let action_clear_doc = gio::SimpleAction::new("clear-doc", None);
        self.add_action(&action_clear_doc);
        let action_new_doc = gio::SimpleAction::new("new-doc", None);
        self.add_action(&action_new_doc);
        let action_save_doc = gio::SimpleAction::new("save-doc", None);
        self.add_action(&action_save_doc);
        let action_save_doc_as = gio::SimpleAction::new("save-doc-as", None);
        self.add_action(&action_save_doc_as);
        let action_autosave = gio::PropertyAction::new("autosave", self, "autosave");
        self.add_action(&action_autosave);
        let action_open_doc = gio::SimpleAction::new("open-doc", None);
        self.add_action(&action_open_doc);
        let action_print_doc = gio::SimpleAction::new("print-doc", None);
        self.add_action(&action_print_doc);
        let action_import_file = gio::SimpleAction::new("import-file", None);
        self.add_action(&action_import_file);
        let action_export_doc = gio::SimpleAction::new("export-doc", None);
        self.add_action(&action_export_doc);
        let action_export_doc_pages = gio::SimpleAction::new("export-doc-pages", None);
        self.add_action(&action_export_doc_pages);
        let action_export_selection = gio::SimpleAction::new("export-selection", None);
        self.add_action(&action_export_selection);
        let action_clipboard_copy = gio::SimpleAction::new("clipboard-copy", None);
        self.add_action(&action_clipboard_copy);
        let action_clipboard_cut = gio::SimpleAction::new("clipboard-cut", None);
        self.add_action(&action_clipboard_cut);
        let action_clipboard_paste = gio::SimpleAction::new("clipboard-paste", None);
        self.add_action(&action_clipboard_paste);
        let action_clipboard_paste_contextmenu =
            gio::SimpleAction::new("clipboard-paste-contextmenu", None);
        self.add_action(&action_clipboard_paste_contextmenu);
        let action_active_tab_move_left = gio::SimpleAction::new("active-tab-move-left", None);
        self.add_action(&action_active_tab_move_left);
        let action_active_tab_move_right = gio::SimpleAction::new("active-tab-move-right", None);
        self.add_action(&action_active_tab_move_right);
        let action_active_tab_close = gio::SimpleAction::new("active-tab-close", None);
        self.add_action(&action_active_tab_close);

        let action_drawing_pad_pressed_button_0 =
            gio::SimpleAction::new("drawing-pad-pressed-button-0", None);
        self.add_action(&action_drawing_pad_pressed_button_0);
        let action_drawing_pad_pressed_button_1 =
            gio::SimpleAction::new("drawing-pad-pressed-button-1", None);
        self.add_action(&action_drawing_pad_pressed_button_1);
        let action_drawing_pad_pressed_button_2 =
            gio::SimpleAction::new("drawing-pad-pressed-button-2", None);
        self.add_action(&action_drawing_pad_pressed_button_2);
        let action_drawing_pad_pressed_button_3 =
            gio::SimpleAction::new("drawing-pad-pressed-button-3", None);
        self.add_action(&action_drawing_pad_pressed_button_3);

        // Open settings
        action_open_settings.connect_activate(clone!(
            #[weak(rename_to = appwindow)]
            self,
            move |_, _| {
                appwindow
                    .sidebar()
                    .sidebar_stack()
                    .set_visible_child_name("settings_page");
                appwindow.split_view().set_show_sidebar(true);
            }
        ));

        // About Dialog
        action_about.connect_activate(clone!(
            #[weak(rename_to=appwindow)]
            self,
            move |_, _| {
                dialogs::dialog_about(&appwindow);
            }
        ));

        // Donate
        action_donate.connect_activate(clone!(move |_, _| {
            UriLauncher::new(config::APP_DONATE_URL).launch(
                None::<&Window>,
                gio::Cancellable::NONE,
                |res| {
                    if let Err(e) = res {
                        error!("Launching donate URL failed, Err: {e:?}");
                    }
                },
            )
        }));

        // Keyboard shortcuts
        action_keyboard_shortcuts_dialog.connect_activate(clone!(
            #[weak(rename_to=appwindow)]
            self,
            move |_, _| {
                dialogs::dialog_keyboard_shortcuts(&appwindow);
            }
        ));

        // Open Canvas Menu
        action_open_canvasmenu.connect_activate(clone!(
            #[weak(rename_to=appwindow)]
            self,
            move |_, _| {
                if appwindow.split_view().shows_sidebar() && appwindow.split_view().is_collapsed() {
                    appwindow.split_view().set_show_sidebar(false);
                }
                // appwindow.main_header().canvasmenu().popovermenu().popup();
            }
        ));

        // Open App Menu
        action_open_appmenu.connect_activate(clone!(
            #[weak(rename_to=appwindow)]
            self,
            move |_, _| {
                if !appwindow.split_view().shows_sidebar() {
                    appwindow.main_header().appmenu().popovermenu().popup();
                    return;
                }
                if appwindow.split_view().is_collapsed() {
                    appwindow.split_view().set_show_sidebar(false);
                    appwindow.main_header().appmenu().popovermenu().popup();
                } else {
                    appwindow.sidebar().appmenu().popovermenu().popup();
                }
            }
        ));

        // Toggle Tabs Overview
        action_toggle_overview.connect_activate(clone!(
            #[weak(rename_to=appwindow)]
            self,
            move |_, _| {
                let overview = appwindow.overview();
                overview.set_open(!overview.is_open());
            }
        ));

        // Developer mode
        action_devel_mode.connect_activate(clone!(
            #[weak]
            action_devel_menu,
            #[weak]
            action_visual_debug,
            move |action, _| {
                let state = action.state().unwrap().get::<bool>().unwrap();

                // Enable the devel menu action to reveal it in the app menu
                action_devel_menu.set_enabled(!state);

                // Always disable visual-debugging when disabling the developer mode
                if state {
                    debug!("Disabling developer mode, disabling visual debugging.");
                    action_visual_debug.change_state(&false.to_variant());
                }
                action.change_state(&(!state).to_variant());
            }
        ));

        // Developer settings
        // Its enabled state toggles the visibility of the developer settings menu entry.
        // Must only be modified inside the devel-mode action
        action_devel_menu.set_enabled(false);


        // Open doc
        action_open_doc.connect_activate(clone!(
            #[weak(rename_to=appwindow)]
            self,
            move |_, _| {
                glib::spawn_future_local(clone!(
                    #[weak]
                    appwindow,
                    async move {
                        dialogs::import::filedialog_open_doc(&appwindow).await;
                    }
                ));
            }
        ));


        // Import
        action_import_file.connect_activate(clone!(
            #[weak(rename_to=appwindow)]
            self,
            move |_, _| {
                glib::spawn_future_local(clone!(
                    #[weak]
                    appwindow,
                    async move {
                        dialogs::import::filedialog_import_file(&appwindow).await;
                    }
                ));
            }
        ));


        // Clipboard paste
        action_clipboard_paste.connect_activate(clone!(
            #[weak(rename_to=appwindow)]
            self,
            move |_, _| {
                appwindow.clipboard_paste(None);
            }
        ));
    }

    pub(crate) fn setup_action_accels(&self) {
        let app = self.app();

        app.set_accels_for_action("win.active-tab-close", &["<Ctrl>w"]);
        app.set_accels_for_action("win.fullscreen", &["F11"]);
        app.set_accels_for_action("win.keyboard-shortcuts", &["<Ctrl>question"]);
        app.set_accels_for_action("win.toggle-overview", &["<Ctrl><Shift>o"]);
        app.set_accels_for_action("win.open-canvasmenu", &["F9"]);
        app.set_accels_for_action("win.open-appmenu", &["F10"]);
        app.set_accels_for_action("win.open-doc", &["<Ctrl>o"]);
        app.set_accels_for_action("win.save-doc", &["<Ctrl>s"]);
        app.set_accels_for_action("win.save-doc-as", &["<Ctrl><Shift>s"]);
        app.set_accels_for_action("win.new-tab", &["<Ctrl>t"]);
        app.set_accels_for_action("win.snap-positions", &["<Ctrl><Shift>p"]);
        app.set_accels_for_action("win.clear-doc", &["<Ctrl>l"]);
        app.set_accels_for_action("win.print-doc", &["<Ctrl>p"]);
        app.set_accels_for_action("win.add-page-to-doc", &["<Ctrl><Shift>a"]);
        app.set_accels_for_action("win.remove-page-from-doc", &["<Ctrl><Shift>r"]);
        app.set_accels_for_action("win.zoom-in", &["<Ctrl>plus"]);
        app.set_accels_for_action("win.zoom-out", &["<Ctrl>minus"]);
        app.set_accels_for_action("win.import-file", &["<Ctrl>i"]);
        app.set_accels_for_action("win.undo", &["<Ctrl>z"]);
        app.set_accels_for_action("win.redo", &["<Ctrl><Shift>z"]);
        app.set_accels_for_action("win.clipboard-copy", &["<Ctrl>c"]);
        app.set_accels_for_action("win.clipboard-cut", &["<Ctrl>x"]);
        app.set_accels_for_action("win.clipboard-paste", &["<Ctrl>v"]);
        app.set_accels_for_action("win.pen-style::brush", &["<Ctrl>1"]);
        app.set_accels_for_action("win.pen-style::shaper", &["<Ctrl>2"]);
        app.set_accels_for_action("win.pen-style::typewriter", &["<Ctrl>3"]);
        app.set_accels_for_action("win.pen-style::eraser", &["<Ctrl>4"]);
        app.set_accels_for_action("win.pen-style::selector", &["<Ctrl>5"]);
        app.set_accels_for_action("win.pen-style::tools", &["<Ctrl>6"]);

        // shortcuts for devel build
        if config::PROFILE.to_lowercase().as_str() == "devel" {
            app.set_accels_for_action("win.visual-debug", &["<Ctrl><Shift>v"]);
        }
    }

    fn clipboard_paste(&self, target_pos: Option<na::Vector2<f64>>) {
        let content_formats = self.clipboard().formats();
        // let Some(canvas) = self.active_tab_canvas() else {
        //     return;
        // };

        // Order matters here, we want to go from specific -> generic, mostly because `text/plain` is contained in other text based formats
        if content_formats.contain_mime_type("text/uri-list") {
            glib::spawn_future_local(clone!(
                #[weak(rename_to=appwindow)]
                self,
                async move {
                    debug!("Recognized clipboard content format: files list");

                    match appwindow.clipboard().read_text_future().await {
                        Ok(Some(text)) => {
                            let file_paths = text
                                .lines()
                                .filter_map(|line| {
                                    let file_path = if let Ok(path_uri) = url::Url::parse(line) {
                                        path_uri.to_file_path().ok()?
                                    } else {
                                        PathBuf::from(&line)
                                    };

                                    if file_path.exists() {
                                        Some(file_path)
                                    } else {
                                        None
                                    }
                                })
                                .collect::<Vec<PathBuf>>();

                            for file_path in file_paths {
                                // appwindow
                                //     .open_file_w_dialogs(
                                //         gio::File::for_path(&file_path),
                                //         target_pos,
                                //         true,
                                //     )
                                //     .await;
                            }
                        }
                        Ok(None) => {}
                        Err(e) => {
                            error!("Reading clipboard text while pasting clipboard from path failed, Err: {e:?}");
                        }
                    }
                }
            ));
        } else if content_formats.contain_mime_type("image/svg+xml") {
            glib::spawn_future_local(clone!(
                #[weak(rename_to=appwindow)]
                self,
                async move {
                    debug!("Recognized clipboard content: svg image");

                    match appwindow
                        .clipboard()
                        .read_future(&["image/svg+xml"], glib::source::Priority::DEFAULT)
                        .await
                    {
                        Ok((input_stream, _)) => {
                            let mut acc = Vec::new();
                            loop {
                                match input_stream
                                    .read_future(
                                        vec![0; CLIPBOARD_INPUT_STREAM_BUFSIZE],
                                        glib::source::Priority::DEFAULT,
                                    )
                                    .await
                                {
                                    Ok((mut bytes, n)) => {
                                        if n == 0 {
                                            break;
                                        }
                                        acc.append(&mut bytes);
                                    }
                                    Err(e) => {
                                        error!("Failed to read clipboard input stream while pasting as Svg, Err: {e:?}");
                                        acc.clear();
                                        break;
                                    }
                                }
                            }

                            if !acc.is_empty() {
                                match crate::utils::str_from_u8_nul_utf8(&acc) {
                                Ok(text) => {
                                    // if let Err(e) = canvas.load_in_vectorimage_bytes(text.as_bytes().to_vec(), target_pos, appwindow.respect_borders()).await {
                                    //     error!(
                                    //         "Loading VectorImage bytes failed while pasting as Svg failed, Err: {e:?}"
                                    //     );
                                    // };
                                }
                                Err(e) => error!("Failed to get string from clipboard data while pasting as Svg, Err: {e:?}"),
                            }
                            }
                        }
                        Err(e) => {
                            error!(
                                "Failed to read clipboard data while pasting as Svg, Err: {e:?}"
                            );
                        }
                    };
                }
            ));
        } else if content_formats.contain_mime_type("image/png")
            || content_formats.contain_mime_type("image/jpeg")
            || content_formats.contain_mime_type("image/jpg")
            || content_formats.contain_mime_type("image/tiff")
            || content_formats.contain_mime_type("image/bmp")
        {
            const MIMES: [&str; 5] = [
                "image/png",
                "image/jpeg",
                "image/jpg",
                "image/tiff",
                "image/bmp",
            ];
            if let Some(mime_type) = MIMES
                .into_iter()
                .find(|&mime| content_formats.contain_mime_type(mime))
            {
                glib::spawn_future_local(clone!(
                    // #[weak]
                    // canvas,
                    #[weak(rename_to=appwindow)]
                    self,
                    async move {
                        debug!("Recognized clipboard content: bitmap image");

                        match appwindow.clipboard().read_texture_future().await {
                            Ok(Some(texture)) => {
                                // if let Err(e) = canvas
                                //     .load_in_bitmapimage_bytes(
                                //         texture.save_to_png_bytes().to_vec(),
                                //         target_pos,
                                //         appwindow.respect_borders(),
                                //     )
                                //     .await
                                // {
                                //     error!(
                                //         "Loading bitmap image bytes failed while pasting clipboard as {mime_type}, Err: {e:?}"
                                //     );
                                // };
                            }
                            Ok(None) => {}
                            Err(e) => {
                                error!(
                                    "Reading clipboard text failed while pasting clipboard as {mime_type}, Err: {e:?}"
                                );
                            }
                        };
                    }
                ));
            }
        } else if content_formats.contain_mime_type("text/plain")
            || content_formats.contain_mime_type("text/plain;charset=utf-8")
        {
            glib::spawn_future_local(clone!(
                // #[weak]
                // canvas,
                #[weak(rename_to=appwindow)]
                self,
                async move {
                    debug!("Recognized clipboard content: plain text");

                    match appwindow.clipboard().read_text_future().await {
                        Ok(Some(text)) => {
                            // if let Err(e) = canvas.load_in_text(text.to_string(), target_pos) {
                            //     error!("Failed to paste clipboard text, Err: {e:?}");
                            // }
                        }
                        Ok(None) => {}
                        Err(e) => {
                            error!(
                            "Reading clipboard text failed while pasting clipboard as plain text, Err: {e:?}"
                        );
                        }
                    }
                }
            ));
        } else {
            debug!(
                "Failed to paste clipboard, unsupported MIME-type(s): {:?}",
                content_formats.mime_types()
            );
        }
    }
}
